#!/usr/bin/env python3
"""
    Produce information about third-party libraries and compilation flags

    When running from shell, return 0 on success, some other value otherwise

"""
import argparse
import glob
import os
import re
import subprocess
import sys


def expand_path(path):
    """Expands variables from the given path and turns it into absolute path"""

    return os.path.abspath(os.path.expanduser(os.path.expandvars(path)))


def setup():
    """Parse command-line arguments"""

    prsr = argparse.ArgumentParser(
        description="Produce info about 3p libraries and compilation flags",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    prsr.add_argument(
        "--repos", help="Path to root of the xNVMe repository", required=True
    )
    args = prsr.parse_args()
    args.repos = expand_path(args.repos)

    return args


def run(cmd):
    """Execute the given command"""

    with subprocess.Popen(
        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="UTF-8"
    ) as proc:

        out, err = proc.communicate()

        return out, err, proc.returncode


def git_head_rev_name(repos):
    """Return the git branch of given repository"""

    return run(["git", "-C", str(repos), "rev-parse", "--abbrev-ref", "HEAD"])


def git_head_rev_short(repos):
    """Return the git branch of given repository"""

    return run(["git", "-C", str(repos), "rev-parse", "--short", "HEAD"])


def git_describe(repos):
    """Try running git describe on the given repos"""

    return run(["git", "-C", str(repos), "describe", "--tags", "--abbrev=8"])


def gen_description(project):
    """Produce a string which identifies the given project and its version"""

    if not os.path.exists(project["path"]["repos"]):  # Info from repos
        return None

    out, _, rcode = git_describe(project["path"]["repos"])
    if not rcode:
        return "git-describe:%s" % out.strip()

    descr = []

    out, _, rcode = git_head_rev_name(project["path"]["repos"])
    if not rcode:
        descr.append("%s" % out.strip())

    out, _, rcode = git_head_rev_short(project["path"]["repos"])
    if not rcode:
        descr.append("%s" % out.strip())

    return "git-rev:%s" % "/".join(descr) if descr else None


def traverse_projects(args):
    """Traverse third-party projects / repositories"""

    dirname = os.path.join(args.repos, "subprojects")
    for path in sorted(
        f.split(".")[0] for f in glob.glob(os.path.join(dirname, "*.wrap"))
    ):
        vfields = ["name", "descr", "patches"]

        projname = os.path.basename(path)

        project = dict.fromkeys(vfields, "unknown")
        project["name"] = projname
        project["path"] = {
            "repos": os.path.join(dirname, projname),
            "patches": os.path.join(dirname, f"packagefiles/{projname}/patches"),
        }
        project["patches"] = (
            "+patches"
            if len(glob.glob(os.path.join(project["path"]["patches"], "*.patch"))) > 0
            else ""
        )
        if not os.path.exists(project["path"]["repos"]):
            continue

        descr = gen_description(project)
        if descr:
            project["descr"] = descr

        project["ver"] = "3p: " + ";".join(
            (project[field] for field in vfields if project[field])
        )

        yield project, descr is None


def gen_flags(args):
    """Produce a list of strings from the compilation flags in meson.build"""
    regex = re.compile(r"conf_data.set\('(?P<flag>.+_ENABLED)'")
    flags = []
    with open(os.path.join(args.repos, "meson.build")) as mfd:
        for line in mfd.readlines():
            match = regex.match(line)
            if match:
                flags.append(match.group("flag"))

    return flags


def update(args):
    """Generate a file containing the version strings and compilation flags"""

    failures = []

    ver = "///< This file is autogenerated by 'toolbox/xnvme_libconf.py'\n"
    ver += "const char *xnvme_libconf[] = {\n"
    for project, err in traverse_projects(args):
        print("project: %s, success: %r" % (project["name"], not err))
        if err:
            failures.append(project)
            continue

        guard = args.guards[project["name"]]

        if not guard:
            ver += '\t"%s",\n' % (project["ver"])
            continue

        ver += "#ifdef %s\n" % guard
        ver += '\t"%s",\n' % (project["ver"])
        ver += "#else\n"
        ver += '\t"%s;NOSYS",\n' % (project["name"])
        ver += "#endif\n"

    for flag in gen_flags(args):
        ver += "#ifdef %s\n" % flag
        ver += '\t"conf: %s",\n' % flag
        ver += "#endif\n"

    ver += "\t0,\t///< For array-termination\n"
    ver += "};\n"

    if failures:
        print("Got failures -- not updating")
        return 1

    with open(os.path.join(args.repos, "lib", "xnvme_libconf_entries.c"), "wt") as vfd:
        vfd.write(ver)

    return 0


def main(args):
    """Entry point"""

    args.guards = {
        "fio": None,
        "spdk": "XNVME_BE_SPDK_ENABLED",
        "libnvme": "XNVME_BE_LINUX_ENABLED",
        "libvfn": "XNVME_BE_LINUX_VFIO_ENABLED",
    }

    try:
        update(args)
    except FileNotFoundError:
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main(setup()))
